/*           Copyright(c) 2005-2008 Dave Sexton            *
 *                   HtmlEditor Control                    *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using mshtml;
using System.Threading;
using System.Net;
using DaveSexton.Controls.Native;
using DaveSexton.Controls.Resources;

namespace DaveSexton.Controls
{
  public enum HtmlChangedReason
  {
    DocumentLoaded, 
    UserEdit, 
    CommandNotification, 
    Clear, 
    Normalize
  }

	[DefaultEvent("EditorReady"), DefaultProperty("Title")]
	public partial class HtmlEditor : WebBrowser
	{
		#region Public Properties
		public static readonly Padding DefaultBodyMargin = new Padding(10, 15, 10, 15);

		#region Browsable(true)
		/// <summary>
		/// Gets or sets whether editing glyphs are visible for HTML tags in teh current <see cref="Document"/>.
		/// </summary>
		[Category("Appearance"), DefaultValue(false), Description("Indicates whether editing glyphs are visible for HTML tags in the current document.")]
		public bool TagsVisible
		{
			get
			{
				return tagsVisible;
			}
			set
			{
				HtmlCommand.Execute(HtmlCommand.MSHtml.ShowAllTags, this, tagsVisible = value, false);
			}
		}

		/// <summary>
		/// Gets or sets whether white-space between paragraphs should be allowed when a new document is loaded.
		/// </summary>
		[Category("Behavior"), DefaultValue(true), Description("Indicates whether white-space between paragraphs should be allowed when a new document is loaded.")]
		public bool PreserveParagraphMargins
		{
			get
			{
				return preserveParagraphMargins;
			}
			set
			{
				preserveParagraphMargins = value;
			}
		}

		/// <summary>
		/// Gets or sets whether the editor can be used as a drop target for drag and drop operations.
		/// </summary>
		[Category("Behavior"), DefaultValue(true), Description("Indicates whether the editor can be used as a drop target for drag and drop operations.")]
		public bool DragDropAllowed
		{
			get
			{
				return base.AllowWebBrowserDrop;
			}
			set
			{
				base.AllowWebBrowserDrop = value;
			}
		}

		/// <summary>
		/// Gets or sets whether the <strong>Insert Picture</strong> context menu item is shown.
		/// </summary>
		[Category("Behavior"), DefaultValue(true), Description("Indicates whether the Insert Picture context menu item is shown.")]
		public bool CanInsertPicture
		{
			get
			{
				return canInsertPicture;
			}
			set
			{
				mnuPicture.Visible = canInsertPicture = value;
			}
		}

		/// <summary>
		/// Gets or sets whether the editor's context menu will be shown when the user right-mouse clicks the document.
		/// </summary>
		[Category("Behavior"), DefaultValue(true), Description("Indicates whether the editor's context menu will be shown when the user right-mouse clicks the document.")]
		public bool ContextMenuEnabled
		{
			get
			{
				return enableContextMenu;
			}
			set
			{
				enableContextMenu = value;

				if (contextMenu.Visible)
					contextMenu.Hide();
			}
		}

		[Category("Appearance")]
		public Padding BodyMargin
		{
			get
			{
				if (initialized)
				{
					IHTMLCurrentStyle body = ((IHTMLElement2) Document.Body.DomElement).currentStyle;

					System.Globalization.CultureInfo culture = System.Globalization.CultureInfo.InvariantCulture;

					return new Padding(
						(body.marginLeft == null) ? 0 : (int) System.Web.UI.WebControls.Unit.Parse(body.marginLeft.ToString(), culture).Value,
						(body.marginTop == null) ? 0 : (int) System.Web.UI.WebControls.Unit.Parse(body.marginTop.ToString(), culture).Value,
						(body.marginRight == null) ? 0 : (int) System.Web.UI.WebControls.Unit.Parse(body.marginRight.ToString(), culture).Value,
						(body.marginBottom == null) ? 0 : (int) System.Web.UI.WebControls.Unit.Parse(body.marginBottom.ToString(), culture).Value);
				}
				else
					return preInitBodyMargin.GetValueOrDefault(DefaultBodyMargin);
			}
			set
			{
				if (initialized)
					SetBodyMarginInternal(value);
				else
					preInitBodyMargin = value;
			}
		}

		private bool ShouldSerializeBodyMargin()
		{
			return BodyMargin != DefaultBodyMargin;
		}

		private void ResetBodyMargin()
		{
			BodyMargin = DefaultBodyMargin;
		}

		/// <summary>
		/// Gets or sets the editor's HTML template as a file with the specified uniform resource identifier.
		/// </summary>
		/// <remarks>
		/// The template must contain valid HTML markup.
		/// <para>
		/// Optionally, a single <c>$BODY$</c> token that indicates the placement of the body HTML may be specified for when the <see cref="Body"/> 
		/// property is assigned.
		/// </para>
		/// </remarks>
		[Category("Appearance"), DefaultValue(null), Description("Specifies the editor's HTML template as a file with the specified uniform resource identifier.  The template must contain valid HTML markup.  " +
		 "Optionally, a single $BODY$ token that indicates the placement of the body HTML can be included for when the Body property is assigned."),
		Editor(typeof(Design.TemplateUrlEditor), typeof(System.Drawing.Design.UITypeEditor))]
		public Uri TemplateUrl
		{
			get
			{
				return templateUrl;
			}
			set
			{
				this.templateUrl = value;
				loadedTemplateUrl = false;

				if (initialized)
				// If the editor hasn't been initialized yet then the template will be loaded when it is;
				// otherwise, the template must be loaded immediately:
				{
					LoadTemplate();

					// must read the entire stream again to clear the dirty flag.
					ReadDocument(true);
				}
			}
		}

		/// <summary>
		/// Gets or sets the editor's HTML template as raw HTML markup.
		/// </summary>
		/// <remarks>
		/// The template must contain valid HTML markup.
		/// <para>
		/// Optionally, a single <c>$BODY$</c> token that indicates the placement of the body HTML may be specified for when the <see cref="Body"/> 
		/// property is assigned.
		/// </para>
		/// </remarks>
		[Category("Appearance"), Description("Specifies the editor's HTML template as raw HTML markup.  " +
		 "Optionally, a single $BODY$ token that indicates the placement of the body HTML can be included for when the Body property is assigned."), 
		RefreshProperties(RefreshProperties.All)]
		[Editor(typeof(System.ComponentModel.Design.MultilineStringEditor), typeof(System.Drawing.Design.UITypeEditor))]
		public string TemplateHtml
		{
			get
			{
				return template;
			}
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				templateUrl = null;
				this.template = value;

				if (initialized)
				// If the editor hasn't been initialized yet then the template will be loaded when it is;
				// otherwise, the template must be loaded immediately:
				{
					LoadTemplate();

					// must read the entire stream again to clear the dirty flag.
					ReadDocument(true);
				}
			}
		}

		private bool ShouldSerializeTemplateHtml()
		{
			string html = TemplateHtml;
			return html == null || !html.Equals(HtmlResources.DefaultHtml, StringComparison.Ordinal);
		}

		private void ResetTemplateHtml()
		{
			TemplateHtml = HtmlResources.DefaultHtml;
		}
		#endregion

		#region Browsable(false)
		[Browsable(false)]
		public bool IsReady
		{
			get
			{
				return ReadyState == WebBrowserReadyState.Complete;
			}
		}

		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public HtmlEditorCommands Commands
		{
			get
			{
				return commands;
			}
		}

		/// <summary>
		/// Gets or sets the html in the editor.
		/// </summary>
		/// <remarks>
		/// Setting this property will replace the entire html <see cref="Document"/> with the specified value and will
		/// clear all editor state, including the undo context.
		/// <br /><strong>Note:</strong>
		/// <para>
		/// The value of this property will not change until the <see cref="HtmlChanged"/> event is raised.  Blocking the
		/// UI thread will prevent the editor from reinitializing and the <see cref="HtmlChanged"/> event will not be
		/// raised until the UI thread is freed.
		/// </para>
		/// </remarks>
		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public string Html
		{
			get
			{
				return ReadDocument(false);
			}
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				WriteDocument(value);
			}
		}

		/// <summary>
		/// Gets or sets the html in the editor's <strong>body</strong> element.
		/// </summary>
		/// <remarks>
		/// Setting this property will: 
		/// <list type="bullet">
		/// <item>
		///		<term>cause the entire document to be reloaded from the <see cref="TemplateHtml"/>.</term>
		///		<term>replace the <see cref="TemplateHtml"/>'s <c>$BODY$</c> token with the specified html content.</term>
		///		<term>clear all editor state, including the undo context.</term>
		/// </item>
		/// </list>
		/// <br /><strong>Note:</strong>
		/// <para>
		/// The value of this property will not change until the <see cref="HtmlChanged"/> event is raised.  Blocking the
		/// UI thread will prevent the editor from reinitializing and the <see cref="HtmlChanged"/> event will not be
		/// raised until the UI thread resumes processing messages.
		/// </para>
		/// </remarks>
		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public string Body
		{
			get
			{
				if (domDocument == null)
					// Although domDocument is not used below the Document property will throw NullReferenceException
					// if it was not loaded yet and domDocument is set after the Document has loaded.
					throw new InvalidOperationException(Errors.EditorNotReady);
				else
					return domDocument.body.innerHTML ?? string.Empty;		// null occurred in testing
			}
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				WriteDocument(CreateDocumentHtml(value));
			}
		}

		/// <summary>
		/// Gets or sets whether the document has changed since the last time the <see cref="HtmlChanged"/> event was raised.
		/// </summary>
		/// <remarks>
		/// The <see cref="HtmlChanged"/> event is raised when the <see cref="Document" /> loses focus if this property is <strong>true</strong>; however, after resizing a control
		/// in edit mode (e.g., a table element or an image), clicking away from the document will not cause the <see cref="HtmlDocument.LosingFocus" /> event to be raised since 
		/// the document doesn't actually have the focus until the resize operation is comitted.  Normally, the resize operation is commited by clicking the document, at which time 
		/// the <see cref="HtmlChanged" /> event <em>will</em> be raised.  Therefore, exposing this property to clients allows them to verify whether the <see cref="HtmlChanged" /> 
		/// event had been raised the last time it was expected; otherwise, the <see cref="Html" /> property may be read immediately instead of waiting for the next event.
		/// <para>
		/// This property can be set to <strong>false</strong> so that the control will commit any changes, which will prevent the <see cref="HtmlChanged" /> event from being raised the 
		/// next time the document focus is lost.  Setting this property to <strong>true</strong> will cause an <see cref="ArgumentException" /> to be thrown.
		/// </para>
		/// <para>
		/// This property is also useful for unsupported commands or script that may manipulate the DOM since they will not notify the 
		/// <see cref="HtmlEditor" /> control that the <see cref="Html" /> has changed, and so the <see cref="HtmlChanged" /> event will not be raised.  
		/// However, the <see cref="HtmlCommand" /> class will handle notification automatically after the execution of many commands that have the potential 
		/// to change the document.
		/// </para>
		/// </remarks>
		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public bool IsDirty
		{
			get
			{
				IPersistStreamInit doc = (IPersistStreamInit) domDocument;

				return doc.IsDirty() == Constants.S_OK;
			}
			set
			{
				if (value)
					throw new ArgumentException(Errors.IsDirtyCannotBeSetToTrue);

				HtmlStream.ClearIsDirty((IPersistStreamInit) domDocument);
			}
		}

		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public virtual HtmlElement DefaultEditableElement
		{
			get
			{
				if (initialized && defaultElement == null)
					throw new InvalidOperationException(Errors.NoEditableContent);

				return defaultElement;
			}
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				defaultElement = value;
			}
		}

		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public bool StandardsCompliantMode
		{
			get
			{
				return ((IHTMLDocument5) domDocument).compatMode == "CSS1Compat";
			}
		}

		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public Font SelectedFont
		{
			get
			{
				string name = Commands.FontName.GetValue("Times New Roman");
				float size = ConvertFontSizeToPoints(Commands.FontSize.GetValue(3));

				return new Font(name, size, SelectedFontStyle);
			}
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				Commands.FontName.Execute(false, value.Name);
				Commands.FontSize.Execute(false, ConvertFontSizeToHtml(value.SizeInPoints));

				SelectedFontStyle = value.Style;
			}
		}

		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public int SelectedHtmlFontSize
		{
			get
			{
				return Commands.FontSize.GetValue(3);
			}
			set
			{
				Commands.FontSize.Execute(false, value);
			}
		}

		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public FontStyle SelectedFontStyle
		{
			get
			{
				FontStyle style = FontStyle.Regular;

				if (Commands.Bold.Activated)
					style |= FontStyle.Bold;

				if (Commands.Italic.Activated)
					style |= FontStyle.Italic;

				if (Commands.Underline.Activated)
					style |= FontStyle.Underline;

				if (Commands.Strikeout.Activated)
					style |= FontStyle.Strikeout;

				return style;
			}
			set
			{
				if ((value & FontStyle.Bold) != 0 && !Commands.Bold.Activated)
					// toggle on bold
					Commands.Bold.Execute();
				else if ((value & FontStyle.Bold) != 0 && Commands.Bold.Activated)
					// toggle off bold
					Commands.Bold.Execute();

				if ((value & FontStyle.Italic) != 0 && !Commands.Italic.Activated)
					// toggle on italic
					Commands.Italic.Execute();
				else if ((value & FontStyle.Italic) != 0 && Commands.Italic.Activated)
					// toggle off italic
					Commands.Italic.Execute();

				if ((value & FontStyle.Underline) != 0 && !Commands.Underline.Activated)
					// toggle on underline
					Commands.Underline.Execute();
				else if ((value & FontStyle.Underline) != 0 && Commands.Underline.Activated)
					// toggle off underline
					Commands.Underline.Execute();

				if ((value & FontStyle.Strikeout) != 0 && !Commands.Strikeout.Activated)
					// toggle on strikeout
					Commands.Strikeout.Execute();
				else if ((value & FontStyle.Strikeout) != 0 && Commands.Strikeout.Activated)
					// toggle off strikeout
					Commands.Strikeout.Execute();
			}
		}

		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public Color SelectedForeColor
		{
			get
			{
				HtmlCommand command = Commands.ForeColor;

				object color = command.GetValue<object>(null);

				return (color != null && !command.Indeterminate)
					? (color is int)
						? ColorTranslator.FromOle((int) color)			// in testing queryCommandValue always returned an OLE value
						: ColorTranslator.FromHtml((string) color)
					: Color.Black;
			}
			set
			{
				HtmlCommand command = Commands.ForeColor;

				if (command.Supported && command.Enabled)
					command.Execute(false, ColorTranslator.ToHtml(value));
			}
		}

		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public Color SelectedBackColor
		{
			get
			{
				HtmlCommand command = Commands.BackColor;

				object color = command.GetValue<object>(null);

				return (color != null && !command.Indeterminate)
					? (color is int)
						? ColorTranslator.FromOle((int) color)			// in testing queryCommandValue always returned an OLE value
						: ColorTranslator.FromHtml((string) color)
					: Color.Transparent;
			}
			set
			{
				HtmlCommand command = Commands.BackColor;

				if (command.Supported && command.Enabled)
					command.Execute(false, ColorTranslator.ToHtml(value));
			}
		}

		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public HtmlSelectionType SelectionType
		{
			get
			{
				if (domDocument == null)
					throw new InvalidOperationException(Errors.EditorNotReady);

				if (string.Equals(domDocument.selection.type, "control", StringComparison.OrdinalIgnoreCase))
					return HtmlSelectionType.Control;
				else if (string.Equals(domDocument.selection.type, "text", StringComparison.OrdinalIgnoreCase))
					return HtmlSelectionType.Text;
				else
					return HtmlSelectionType.None;
			}
		}

		[Browsable(false)]
		public HtmlSelection Selection
		{
			get
			{
				return new HtmlSelection(this, true);
			}
		}
		#endregion

		#region Hidden Properties
		[Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public new bool AllowNavigation
		{
			get
			{
				return base.AllowNavigation;
			}
			set
			{
				base.AllowNavigation = value;
			}
		}

		[Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public new bool WebBrowserShortcutsEnabled
		{
			get
			{
				return base.WebBrowserShortcutsEnabled;
			}
			set
			{
				base.WebBrowserShortcutsEnabled = value;
			}
		}

		[Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public new bool AllowWebBrowserDrop
		{
			get
			{
				return base.AllowWebBrowserDrop;
			}
			set
			{
				base.AllowWebBrowserDrop = value;
			}
		}

		[Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public new bool ScriptErrorsSuppressed
		{
			get
			{
				return base.ScriptErrorsSuppressed;
			}
			set
			{
				base.ScriptErrorsSuppressed = value;
			}
		}

		[Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public new bool ScrollBarsEnabled
		{
			get
			{
				return base.ScrollBarsEnabled;
			}
			set
			{
				base.ScrollBarsEnabled = value;
			}
		}

		[Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public new bool IsWebBrowserContextMenuEnabled
		{
			get
			{
				return base.IsWebBrowserContextMenuEnabled;
			}
			set
			{
				base.IsWebBrowserContextMenuEnabled = value;
			}
		}

		[Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public new Uri Url
		{
			get
			{
				return base.Url;
			}
			set
			{
				base.Url = value;
			}
		}
		#endregion
		#endregion

		#region Private / Protected
		private IHTMLEventObj WindowEvent
		{
			get
			{
				HtmlDocument document = Document;

				if (document == null)
					// this occurred in testing while an application was shutting down
					return null;
				else
					return ((IHTMLWindow2) Document.Window.DomWindow).@event;
			}
		}

		internal IHTMLElement SelectedControl
		{
			get
			{
				IHTMLElement[] controls = GetSelectedControls();

				return (controls.Length == 0)
					? null
					: controls[0];
			}
		}

		private Native.IServiceProvider Services
		{
			get
			{

				if (services == null)
					services = (Native.IServiceProvider) domDocument;

				return services;
			}
		}

		private Native.IHTMLEditServices2 EditService
		{
			get
			{
				if (editService == null)
				{
					Guid sid = SID_SHTMLEditServices;
					Guid iid = IID_IHTMLEditServices;

					IntPtr svcPtr;
					Services.QueryService(ref sid, ref iid, out svcPtr);

					if (svcPtr == IntPtr.Zero)
						throw new InvalidOperationException(Errors.IServiceProviderUnavailable);

					try
					{
						editService = System.Runtime.InteropServices.Marshal.GetObjectForIUnknown(svcPtr) as Native.IHTMLEditServices2;
					}
					finally
					{
						System.Runtime.InteropServices.Marshal.Release(svcPtr);
					}

					if (editService == null)
						throw new InvalidOperationException(Errors.IHTMLEditService2Unavailable);
				}

				return editService;
			}
		}

		internal IHTMLDocument2 domDocument;

		private HtmlEditorCommands commands;
		/// <summary>
		/// ID of the <see cref="HtmlElement" /> to be focused when the non-editable region of the <see cref="Document" /> is focused by the mouse.
		/// </summary>
		private HtmlElement defaultElement;
		private Uri templateUrl;
		private bool loadedTemplateUrl;
		private Padding? preInitBodyMargin = new Padding(10, 15, 10, 15);
		private string template = HtmlResources.DefaultHtml;
		private bool tagsVisible, preserveParagraphMargins = true, firstTimeInitialize = true;
		private bool canInsertPicture = true, enableContextMenu = true, defaultHtmlWritten, initialized;
		private Native.IServiceProvider services;
		private Native.IHTMLEditServices2 editService;
		private readonly IList<HtmlDesigner> designers = new List<HtmlDesigner>();
		private readonly Dictionary<Uri, IHTMLStyleSheet> styleSheets = new Dictionary<Uri, IHTMLStyleSheet>(0);

		private static readonly Guid SID_SHTMLEditServices = new Guid("3050f7f9-98b5-11cf-bb82-00aa00bdce0b");
		private static readonly Guid IID_IHTMLEditServices = new Guid("3050f663-98b5-11cf-bb82-00aa00bdce0b");
		private static readonly Color SelectedMenuItemBackColor = Color.FromArgb(75, Color.Gray);
		private static readonly Dictionary<Color, Bitmap> colorBitmaps = new Dictionary<Color, Bitmap>();
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="HtmlEditor" /> class.
		/// </summary>
		public HtmlEditor()
		{
			InitializeComponent();

			// drop down menus are set here because the designer throws an Exception if DropDown is set in the designer and the context menu is edited
			mnuForeColor.DropDown = foreColorMenu;
			mnuBackColor.DropDown = backColorMenu;
			mnuLayout.DropDown = layoutMenu;
			mnuInsert.DropDown = insertMenu;

			mnuForeColorTransparent.Image = GetColorBitmap(Color.Transparent);		// this call is required
			mnuForeColorBlack.Image = GetColorBitmap(Color.Black);
			mnuForeColorWhite.Image = GetColorBitmap(Color.White);
			mnuForeColorGray.Image = GetColorBitmap(Color.Gray);
			mnuForeColorRed.Image = GetColorBitmap(Color.Red);
			mnuForeColorGreen.Image = GetColorBitmap(Color.Green);
			mnuForeColorBlue.Image = GetColorBitmap(Color.Blue);
			mnuForeColorYellow.Image = GetColorBitmap(Color.Yellow);
			mnuForeColorOrange.Image = GetColorBitmap(Color.Orange);
			mnuForeColorPurple.Image = GetColorBitmap(Color.Purple);
			mnuForeColorBrown.Image = GetColorBitmap(Color.Brown);

			mnuBackColorTransparent.Image = GetColorBitmap(Color.Transparent);		// this call is required
			mnuBackColorBlack.Image = GetColorBitmap(Color.Black);
			mnuBackColorWhite.Image = GetColorBitmap(Color.White);
			mnuBackColorGray.Image = GetColorBitmap(Color.Gray);
			mnuBackColorRed.Image = GetColorBitmap(Color.Red);
			mnuBackColorGreen.Image = GetColorBitmap(Color.Green);
			mnuBackColorBlue.Image = GetColorBitmap(Color.Blue);
			mnuBackColorYellow.Image = GetColorBitmap(Color.Yellow);
			mnuBackColorOrange.Image = GetColorBitmap(Color.Orange);
			mnuBackColorPurple.Image = GetColorBitmap(Color.Purple);
			mnuBackColorBrown.Image = GetColorBitmap(Color.Brown);

			commands = new HtmlEditorCommands(this);

			Navigate(new Uri("about:blank"));
		}
		#endregion

		#region Methods
		private static Bitmap CreateColorBitmap(Color color)
		{
			Bitmap bitmap = new Bitmap(16, 16);
			bitmap.Tag = color;

			// save the bitmap
			colorBitmaps[color] = bitmap;

			PaintColorBitmap(bitmap, color);

			return bitmap;
		}

		private static Bitmap GetColorBitmap(Color color)
		{
			if (colorBitmaps.ContainsKey(color))
				return colorBitmaps[color];
			else
				return CreateColorBitmap(color);
		}

		private static void PaintColorBitmap(Bitmap bitmap, Color color)
		{
			using (Graphics graphics = Graphics.FromImage(bitmap))
			{
				graphics.FillRectangle(new SolidBrush(color), 0, 0, 16, 16);
			}
		}

		private static float ConvertFontSizeToPoints(int htmlSize)
		{
			// Conversion factor of 4 is used in ConvertFontSizeToHtml as well.
			// 4 was chosen because it calculates the em sizes as close as possible to the standard screen sizes in points: 
			// (according to the docs, htmlSize will be a value between 1 and 7, inclusive)
			/* html																		point
			 * 1																			4
			 * 2																			8
			 * 3 (default font size for browser)			12 
			 * 4																			16
			 * etc.
 			*/
			return htmlSize * 4;
		}

		private static int ConvertFontSizeToHtml(float points)
		{
			// I've chosen a conversion factor of 4 (see ConvertFontSizeToPoints for more info)
			// (according to the docs, htmlSize must be a value between 1 and 7, inclusive)
			return Math.Min(Math.Max((int) (points / 4), 1), 7);
		}

		public static bool ElementContains(object parent, object child)
		{
			if (parent == null)
				throw new ArgumentNullException("parent");

			if (child == null)
				throw new ArgumentNullException("child");

			return ((IHTMLElement) parent).contains((IHTMLElement) child);
		}

		public static bool ElementEqualsOrContains(object parent, object child)
		{
			if (parent == null)
				throw new ArgumentNullException("parent");

			if (child == null)
				throw new ArgumentNullException("child");

			return parent == child ||((IHTMLElement) parent).contains((IHTMLElement) child);
		}

		public static bool ElementContains(object parent, HtmlElement child)
		{
			if (parent == null)
				throw new ArgumentNullException("parent");

			if (child == null)
				throw new ArgumentNullException("child");

			return ((IHTMLElement) parent).contains((IHTMLElement) child.DomElement);
		}

		public static bool ElementEqualsOrContains(object parent, HtmlElement child)
		{
			if (parent == null)
				throw new ArgumentNullException("parent");

			if (child == null)
				throw new ArgumentNullException("child");

			return parent == child.DomElement || ((IHTMLElement) parent).contains((IHTMLElement) child.DomElement);
		}

		public static bool ElementContains(HtmlElement parent, object child)
		{
			if (parent == null)
				throw new ArgumentNullException("parent");

			if (child == null)
				throw new ArgumentNullException("child");

			return ((IHTMLElement) parent.DomElement).contains((IHTMLElement) child);
		}

		public static bool ElementEqualsOrContains(HtmlElement parent, object child)
		{
			if (parent == null)
				throw new ArgumentNullException("parent");

			if (child == null)
				throw new ArgumentNullException("child");

			return parent.DomElement == child || ((IHTMLElement) parent.DomElement).contains((IHTMLElement) child);
		}

		public static bool ElementContains(HtmlElement parent, HtmlElement child)
		{
			if (parent == null)
				throw new ArgumentNullException("parent");

			if (child == null)
				throw new ArgumentNullException("child");

			return ((IHTMLElement) parent.DomElement).contains((IHTMLElement) child.DomElement);
		}

		public static bool ElementEqualsOrContains(HtmlElement parent, HtmlElement child)
		{
			if (parent == null)
				throw new ArgumentNullException("parent");

			if (child == null)
				throw new ArgumentNullException("child");

			return parent == child || ((IHTMLElement) parent.DomElement).contains((IHTMLElement) child.DomElement);
		}

		public static void FocusEnd(HtmlElement element, bool scrollIntoView)
		{
			if (element == null)
				throw new ArgumentNullException("element");

			IHTMLBodyElement body = (IHTMLBodyElement) element.Document.Body.DomElement;
			IHTMLTxtRange range = body.createTextRange();

			range.moveToElementText((IHTMLElement) element.DomElement);
			range.collapse(false);
			range.select();

			if (scrollIntoView)
				range.scrollIntoView(false);
		}

		public static void RemoveElementFromDocument(HtmlElement element, bool leaveChildrenInParent)
		{
			if (element == null)
				throw new ArgumentNullException("element");

      ((IHTMLDOMNode) element.DomElement).removeNode(!leaveChildrenInParent);
		}

		public static HtmlElement FindElement(HtmlDocument document, object element)
		{
			if (document == null)
				throw new ArgumentNullException("document");

			if (element == null)
				throw new ArgumentNullException("element");

			return FindElement(document, (IHTMLElement) element);
		}

		internal static HtmlElement FindElement(HtmlDocument document, IHTMLElement element)
		{
			// internal callers may pass null
			if (element == null)
				return null;

			IHTMLElement body = (IHTMLElement) document.Body.DomElement;

			if (element == body)
				return document.Body;

			Stack<IHTMLElement> parents = new Stack<IHTMLElement>(32);

			IHTMLElement parent = element;

			while ((parent = parent.parentElement) != null && parent != body)
				parents.Push(parent);

			HtmlElement current = document.Body;

			while (parents.Count > 0)
			// find parent recursive
			{
				parent = parents.Pop();

				foreach (HtmlElement child in current.Children)
				{
					if (parent == child.DomElement)
						current = child;
				}
			}

			// find element in current parent
			foreach (HtmlElement child in current.Children)
			{
				if (element == child.DomElement)
					return child;
			}

			throw new ArgumentException(Errors.ElementNotFound, "element");
		}
		
		public void AddStyleSheet(Uri file)
		{
			if (file == null)
				throw new ArgumentNullException("file");

			if (domDocument == null)
				throw new InvalidOperationException(Errors.EditorNotReady);

			domDocument.createStyleSheet(file.ToString(), -1);
		}

		public void RemoveStyleSheet(Uri file)
		{
			if (file == null)
				throw new ArgumentNullException("file");

			if (domDocument == null)
				throw new InvalidOperationException(Errors.EditorNotReady);

			foreach (IHTMLStyleSheet sheet in domDocument.styleSheets)
			{
				if (sheet != null && !string.IsNullOrEmpty(sheet.href) && file.Equals(sheet.href))
				{
					((IHTMLDOMNode) sheet.owningElement).removeNode(true);
					return;
				}
			}

			throw new ArgumentException(string.Format(System.Globalization.CultureInfo.CurrentCulture, 
				Errors.InvalidStyleSheetUri, file), "file");
		}

		public void AddTemplateStyleSheet(Uri file)
		{
			if (file == null)
				throw new ArgumentNullException("file");

			if (initialized)
				styleSheets[file] = domDocument.createStyleSheet(file.ToString(), -1);
			else
				// delay creation until the document is loaded
				styleSheets.Add(file, null);
		}

		public void RemoveTemplateStyleSheet(Uri file)
		{
			if (file == null)
				throw new ArgumentNullException("file");
			
			if (styleSheets.ContainsKey(file))
			{
				IHTMLStyleSheet sheet = styleSheets[file];

				if (sheet != null)
					((IHTMLDOMNode) sheet.owningElement).removeNode(true);

				styleSheets.Remove(file);
			}
		}

		/// <summary>
		/// Two-pass initialization.
		/// InitializeDocument will be called twice - the first time the doucment will be created, and then 
		/// DocumentCompleted will be raised again.  The second time, the document will be initialized.
		/// OnDocumentCompleted
		/// </summary>
		private void InitializeDocument()
		{
			if (!defaultHtmlWritten)
				// this will only occur the first time the control is loaded
				CreateDocument();
			else
				// this will occur any time the WriteDocument method is called (setting the Body or Html
				// properties, for example) when the DocumentCompleted event is raised again.
				InitializeDocumentAfterCreate();
		}

		/// <summary>
		/// This method is the first-pass initialization for the <see cref="InitializeDocument"/> method.
		/// </summary>
		private void CreateDocument()
		{
			domDocument = (IHTMLDocument2) Document.DomDocument;

			// designMode must be enabled instead of just using the contentEditable attribute so that the 
			// Html property will return the document's contents (otherwise, it just returns the original 
			// HTML without any DOM modifications)
			domDocument.designMode = "On";

			WriteDocument(CreateDocumentHtml(string.Empty));

			defaultHtmlWritten = true;
		}

		private string CreateDocumentHtml(string body)
		{
			EnsureTemplateLoadedFromUri();

			return template.Replace("$BODY$", body);
		}

		/// <summary>
		/// Initializes the <see cref="domDocument"/> after it has been created.  This method 
		/// is the second-pass initialization for the <see cref="InitializeDocument"/> method 
		/// and will be invoked once for each call to <see cref="WebBrowser.Navigate"/>, 
		/// and will also be invoked each time the <see cref="Html"/> property is assigned;
		/// although, assigning the <see cref="Html"/> property will not cause the 
		/// <see cref="CreateDocument"/> method to be raised since it only requires a single 
		/// pass to initialize the document, which was already created by the assignment.
		/// </summary>
		private void InitializeDocumentAfterCreate()
		{
			if (designers.Count > 0)
			{
				for (int i = designers.Count - 1; i > -1; i--)
					RemoveDesigner(designers[i]);

				foreach (HtmlDesigner designer in designers)
				{
					HtmlDesigner clone = designer.CreateForNewDocument(domDocument);

					if (clone != null)
						AddDesigner(clone);
				}
			}

			/* (The HostDesigner is intended for use in future versions.  The code has been tested and verified, 
			 * but it's being excluded until it's needed.)*/
			// Designer must be recreated and set each time a document is loaded.
			//AddDesigner(new HostDesigner(this));

			PrepareDocumentForEditing();

			BindToDocumentEvents();

			if (!initialized)
			{
				// must read the entire stream again to clear the dirty flag.
				ReadDocument(true);

				initialized = true;
			}
		}

		private void PrepareDocumentForEditing()
		{
			HtmlElement body = Document.Body;

			defaultElement = FindEditableElementRecursive(true, body);

			if (defaultElement == null)
				return;

			PrepareStyleSheets();

			SetBodyMarginInternal(preInitBodyMargin.GetValueOrDefault(DefaultBodyMargin));

			// SCROLL should be set on the <html> tag for XHTML; however, design mode only supports HTML as of IE7, 
			// which requires SCROLL to be set on the <body> tag.
			body.SetAttribute("SCROLL", "auto");
		}

		private void PrepareStyleSheets()
		{
			if (!preserveParagraphMargins)
			{
				IHTMLStyleSheet styles = domDocument.createStyleSheet(null, -1);

				// this rule ensures that hitting Enter will create a new line immediately after the previous line
				// since the default elemnt is the <P> tag.
				styles.addRule("p", "margin-top: 0px; margin-bottom: 0px;", -1);
			}

			foreach (Uri file in new List<Uri>(styleSheets.Keys))
				styleSheets[file] = domDocument.createStyleSheet(file.ToString(), -1);
		}

		private void BindToDocumentEvents()
		{
			Document.AttachEventHandler("onselectionchange", Document_SelectionChange);
			Document.AttachEventHandler("oncontrolselect", Document_ControlSelect);
			Document.Body.AttachEventHandler("ondrop", Body_Drop);
			Document.Body.DragOver += new HtmlElementEventHandler(Body_DragOver);
			Document.Body.DragLeave += new HtmlElementEventHandler(Body_DragLeave);
			Document.Body.KeyPress += new HtmlElementEventHandler(Body_KeyPress);
			Document.LosingFocus += new HtmlElementEventHandler(Document_LosingFocus);
			Document.Click += new HtmlElementEventHandler(Document_Click);
		}

		private void SetBodyMarginInternal(Padding value)
		{
			HtmlElement body = Document.Body;
			body.SetAttribute("TOPMARGIN", value.Top + "px");
			body.SetAttribute("LEFTMARGIN", value.Left + "px");
			body.SetAttribute("RIGHTMARGIN", value.Right + "px");
			body.SetAttribute("BOTTOMMARGIN", value.Bottom + "px");
		}

		/// <summary>
		/// Writes the specified <paramref name="html"/> as the document, causing the 
		/// <see cref="DocumentCompleted"/> event to be raised, which will clear all browser
		/// state such as the undo context.
		/// </summary>
		/// <remarks>
		/// Unfortunately, the <see cref="DocumentCompleted"/> event is not raised until the UI thread is freed.
		/// Callers should handle the <see cref="HtmlChanged"/> event to be notified when the document has finally
		/// been updated.
		/// <para>
		/// This method is used by the <see cref="Html"/> and <see cref="Body"/> properties' set accessors and
		/// is for internal use only.
		/// </para>
		/// </remarks>
		private void WriteDocument(string html)
		{
			if (domDocument == null)
				throw new InvalidOperationException(Errors.EditorNotReady);

			using (HtmlStream stream = new HtmlStream(Encoding.GetEncoding(domDocument.charset), 
				domDocument, false, HtmlStreamAccess.Write))
			{
				stream.Write(html);
			}
		}

		private string ReadDocument(bool clearDirty)
		{
			if (domDocument == null)
				throw new InvalidOperationException(Errors.EditorNotReady);

			using (HtmlStream stream = new HtmlStream(Encoding.GetEncoding(domDocument.charset), 
				domDocument, clearDirty, HtmlStreamAccess.Read))
			{
				return stream.ReadToEnd();
			}
		}

		private void EnsureTemplateLoadedFromUri()
		{
			if (!loadedTemplateUrl && templateUrl != null)
			{
				using (WebClient client = new WebClient())
				{
					template = client.DownloadString(templateUrl);
				}

				loadedTemplateUrl = true;
			}
		}

		private void LoadTemplate()
		{
			WriteDocument(CreateDocumentHtml(Body));
		}

		public HtmlCommand CreateCommand(HtmlCommandIdentifier command)
		{
			return Commands.GetCommand(command);
		}

		public HtmlCommand CreateCommand(HtmlCommandIdentifier command, object target)
		{
			if (target == null)
				return Commands.GetCommand(command);
			else
				return new HtmlCommand(command, this, target);
		}

		protected internal void NotifyHtmlChanged(HtmlChangedReason reason)
		{
      if (!Document.Focused || !IsDirty)
        // The HtmlChanged event must always be raised if the document isn't focused, but custom commands might not change the web browser's 
        // internal dirty flag, so the changed event should be raised immediately if the document isn't dirty even if it has focus.
        // Otherwise, the event might not be raised for the changes made by the command if they are the only changes made before the 
        // document loses focus.
        OnHtmlChanged(new HtmlChangedEventArgs(reason));
		}

		/// <summary>
		/// Removes the HTML contained by all of the known, content-editable elements.
		/// </summary>
		public void Clear()
		{
			DefaultEditableElement.InnerHtml = string.Empty;

			NotifyHtmlChanged(HtmlChangedReason.Clear);
		}

		public void Print(bool showPageSettings)
		{
			Commands.GetCommand(HtmlCommandIdentifier.Print).Execute(showPageSettings);
		}

		private HtmlElement FindEditableElementRecursive(bool defaultEditable, HtmlElement parentElement)
		{
			string editable = ((IHTMLElement3) parentElement.DomElement).contentEditable;

			if (string.Equals(editable, "false", StringComparison.OrdinalIgnoreCase) || 
				(string.Equals(editable, "inherit", StringComparison.OrdinalIgnoreCase) && !defaultEditable))
			{
				foreach (HtmlElement child in parentElement.Children)
				{
					HtmlElement element = FindEditableElementRecursive(false, child);

					if (element != null)
						return element;
				}
			}
			else if (string.Equals(editable, "true", StringComparison.OrdinalIgnoreCase) ||
				(string.Equals(editable, "inherit", StringComparison.OrdinalIgnoreCase) && defaultEditable))
				return parentElement;

			return null;
		}

		protected virtual bool InitializeContextMenu()
		{
			if (!enableContextMenu)
				return false;

			IHTMLElement srcElement = WindowEvent.srcElement;

			if (srcElement == null || !HtmlSelection.IsEditableElement(srcElement).GetValueOrDefault(true))
				return false;

			SyncMenuItemToCommand(mnuUndo, Commands.Undo, false);
			SyncMenuItemToCommand(mnuRedo, Commands.Redo, false);

			SyncMenuItemToCommand(mnuCut, Commands.Cut, false);
			SyncMenuItemToCommand(mnuCopy, Commands.Copy, false);
			SyncMenuItemToCommand(mnuPaste, Commands.Paste, false);
			SyncMenuItemToCommand(mnuDelete, Commands.Delete, false);

			SyncMenuItemToCommand(mnuBold, Commands.Bold, true);
			SyncMenuItemToCommand(mnuItalic, Commands.Italic, true);
			SyncMenuItemToCommand(mnuUnderline, Commands.Underline, true);

			SyncMenuItemToCommand(mnuIndent, Commands.Indent, true);
			SyncMenuItemToCommand(mnuUnindent, Commands.Outdent, true);

			SyncMenuItemToCommand(mnuJustifyLeft, Commands.JustifyLeft, true);
			SyncMenuItemToCommand(mnuJustifyCenter, Commands.JustifyCenter, true);
			SyncMenuItemToCommand(mnuJustifyRight, Commands.JustifyRight, true);

			SyncMenuItemToCommand(mnuFloatLeft, Commands.FloatLeft, true);
			SyncMenuItemToCommand(mnuFloatRight, Commands.FloatRight, true);

			SyncMenuItemToCommand(mnuFont, Commands.FontName, false);

			SyncColorMenuItemToCommand(mnuForeColor, Commands.ForeColor, foreColorMenu);
			SyncColorMenuItemToCommand(mnuBackColor, Commands.BackColor, backColorMenu);

			IHTMLElement control = SelectedControl;
			IHTMLAnchorElement anchor = control as IHTMLAnchorElement;
			IHTMLImgElement image = control as IHTMLImgElement;

			bool showProperties = anchor != null || image != null;

			mnuProperties.Visible = showProperties;
			mnuPropertiesSeparator.Visible = showProperties;

			HtmlCommand insertImage = Commands.InsertImage;

			if (canInsertPicture)
			{
				if (insertImage.Enabled && insertImage.Supported && image == null)
					mnuPicture.Enabled = true;
				else
					mnuPicture.Enabled = false;
			}

			HtmlCommand createLink = Commands.CreateLink;

			if (createLink.Enabled && createLink.Supported && anchor == null)
				mnuHyperlink.Enabled = true;
			else
				mnuHyperlink.Enabled = false;

			SyncMenuItemToCommand(mnuTable, Commands.InsertTable, false);
			SyncMenuItemToCommand(mnuSelectAll, Commands.SelectAll, false);

			return true;
		}

		private void SyncColorMenuItemToCommand(ToolStripMenuItem item, HtmlCommand command, ContextMenuStrip menuColorList)
		{
			if (command.Supported && command.Enabled)
			{
				item.Enabled = true;

				object colorValue = command.GetValue<object>(null);

				if (colorValue != null && !(colorValue is DBNull))
				// null may be returned when multiple objects are selected (command.Indeterminate is true)
				{
					string colorStr = colorValue as string;

					Color color = (colorStr != null)
						? ColorTranslator.FromHtml(colorStr)
						: ColorTranslator.FromOle((int) colorValue);			// in testing, queryCommandValue always returned the color as an OLE value (IE6)

					item.Image = GetColorBitmap(color);

					// select the menu item that represents the selected color
					foreach (ToolStripItem colorItem in menuColorList.Items)
					{
						if (colorItem is ToolStripMenuItem																		// ignore separators
							&& colorItem != mnuForeColorMore && colorItem != mnuBackColorMore		// hard-coded to prevent errors since Tag is not used
							&& (Color) colorItem.Image.Tag == color)														// Tag is set to the Color associated with its respective item
							colorItem.BackColor = SelectedMenuItemBackColor;
						else
							colorItem.BackColor = Color.Transparent;
					}
				}
				else
					item.Image = null;
			}
			else
			{
				item.Enabled = false;
				item.Image = null;
			}
		}

		private static void SyncMenuItemToCommand(ToolStripMenuItem item, HtmlCommand command, bool displayState)
		{
			item.Enabled = command.Supported && command.Enabled;

			if (displayState)
			{
				if (command.GetValue<bool>(false))
					item.BackColor = SelectedMenuItemBackColor;
				else
					item.BackColor = Color.Transparent;
			}
		}

		private IHTMLElement[] GetSelectedControls()
		{
			List<IHTMLElement> elements = new List<IHTMLElement>();

			if (domDocument != null && string.Equals(domDocument.selection.type, "control", StringComparison.OrdinalIgnoreCase))
			{
				IHTMLControlRange controls = (IHTMLControlRange) domDocument.selection.createRange();

				for (int i = 0; i < controls.length; i++)
					elements.Add(controls.item(i));
			}

			return elements.ToArray();
		}

		public virtual void Normalize()
		{
			((IHTMLElement4) Document.Body.DomElement).normalize();

			NotifyHtmlChanged(HtmlChangedReason.Normalize);
		}

		public void AddDesigner(HtmlDesigner designer)
		{
			if (designer == null)
				throw new ArgumentNullException("designer");

			EditService.AddDesigner(designer);

			designer.Document = Document;

			designers.Add(designer);
		}

		public void RemoveDesigner(HtmlDesigner designer)
		{
			if (designer == null)
				throw new ArgumentNullException("designer");

			EditService.RemoveDesigner(designer);

			designer.Document = null;

			designers.Remove(designer);
		}

		public void ViewSource()
		{
			using (ViewSourceForm form = new ViewSourceForm())
			{
				form.Html = Html;
				form.ShowDialog(FindForm());
			}
		}

		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
		protected override void Dispose(bool disposing)
		{
			try
			{
				if (disposing)
				{
					if (components != null)
						components.Dispose();
				}
			}
			finally
			{
				base.Dispose(disposing);
			}
		}
		#endregion

		#region Events
		#region Hidden Events
#pragma warning disable 0067 // warns that hidden events are never used
		[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
		public new event WebBrowserDocumentCompletedEventHandler DocumentCompleted;

		[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
		public new event WebBrowserNavigatedEventHandler Navigated;

		[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
		public new event WebBrowserNavigatingEventHandler Navigating;
#pragma warning restore 0067
		#endregion

		private readonly object SelectionChangedEvent = new object();
		private readonly object ControlSelectedEvent = new object();
		private readonly object EditorReadyEvent = new object();
		private readonly object HtmlChangedEvent = new object();
		private readonly object KeyPressEvent = new object();
		private readonly object MouseClickUneditableRegionEvent = new object();
		private readonly object DragDropEvent = new object();
		private readonly object DragOverEvent = new object();
		private readonly object DragLeaveEvent = new object();

		/// <summary>
		/// Event raised when the mouse drags content away from the <see cref="Body"/>.
		/// </summary>
		[Browsable(true), Category("Mouse")]
		[Description("Event raised when the mouse drags content away from the body element.")]
		public new event EventHandler<HtmlEditorEventArgs> DragLeave
		{
			add
			{
				lock (DragLeaveEvent)
				{
					Events.AddHandler(DragLeaveEvent, value);
				}
			}
			remove
			{
				lock (DragLeaveEvent)
				{
					Events.RemoveHandler(DragLeaveEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="DragLeave" /> event.
		/// </summary>
		/// <param name="e"><see cref="HtmlEditorEventArgs" /> object that provides the arguments for the event.</param>
		protected virtual void OnDragLeave(HtmlEditorEventArgs e)
		{
			EventHandler<HtmlEditorEventArgs> handler = null;

			lock (DragLeaveEvent)
			{
				handler = (EventHandler<HtmlEditorEventArgs>) Events[DragLeaveEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the mouse drags content over the <see cref="Body"/>.
		/// </summary>
		[Browsable(true), Category("Mouse")]
		[Description("Event raised when the mouse drags content over the body element.")]
		public new event EventHandler<HtmlEditorEventArgs> DragOver
		{
			add
			{
				lock (DragOverEvent)
				{
					Events.AddHandler(DragOverEvent, value);
				}
			}
			remove
			{
				lock (DragOverEvent)
				{
					Events.RemoveHandler(DragOverEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="DragOver" /> event.
		/// </summary>
		/// <param name="e"><see cref="HtmlEditorEventArgs" /> object that provides the arguments for the event.</param>
		protected virtual void OnDragOver(HtmlEditorEventArgs e)
		{
			EventHandler<HtmlEditorEventArgs> handler = null;

			lock (DragOverEvent)
			{
				handler = (EventHandler<HtmlEditorEventArgs>) Events[DragOverEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when an object is dropped on the browser.
		/// </summary>
		[Category("Action")]
		[Description("Event raised when an object is dropped on the browser.")]
		public new event EventHandler<HtmlEditorDragDropEventArgs> DragDrop
		{
			add
			{
				lock (DragDropEvent)
				{
					Events.AddHandler(DragDropEvent, value);
				}
			}
			remove
			{
				lock (DragDropEvent)
				{
					Events.RemoveHandler(DragDropEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="DragDrop" /> event.
		/// </summary>
		/// <param name="e"><see cref="DragEventArgs" /> object that provides the arguments for the event.</param>
		protected virtual void OnDragDrop(HtmlEditorDragDropEventArgs e)
		{
			EventHandler<HtmlEditorDragDropEventArgs> handler = null;

			lock (DragDropEvent)
			{
				handler = (EventHandler<HtmlEditorDragDropEventArgs>) Events[DragDropEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the mouse is clicked inside the editor but outside of any editable element.
		/// </summary>
		/// <remarks>
		/// Set the event arguments <c>Cancel</c> property to <strong>true</strong> to prevent the input focus
		/// from being moved to the <see cref="DefaultEditableElement"/>.
		/// </remarks>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Uneditable")]
		[Category("Action"), Description("Event raised when the mouse is clicked inside the editor but outside of any editable element.")]
		public event EventHandler<HtmlEditorCancelEventArgs> MouseClickUneditableRegion
		{
			add
			{
				lock (MouseClickUneditableRegionEvent)
				{
					Events.AddHandler(MouseClickUneditableRegionEvent, value);
				}
			}
			remove
			{
				lock (MouseClickUneditableRegionEvent)
				{
					Events.RemoveHandler(MouseClickUneditableRegionEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="MouseClickUneditableRegion" /> event.
		/// </summary>
		/// <param name="e"><see cref="EventArgs" /> object that provides the arguments for the event.</param>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Uneditable")]
		protected virtual void OnMouseClickUneditableRegion(HtmlEditorCancelEventArgs e)
		{
			EventHandler<HtmlEditorCancelEventArgs> handler = null;

			lock (MouseClickUneditableRegionEvent)
			{
				handler = (EventHandler<HtmlEditorCancelEventArgs>) Events[MouseClickUneditableRegionEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when a key is pressed and the <see cref="HtmlEditor" /> is focused.
		/// </summary>
		[Category("Action")]
		[Description("Event raised when a key is pressed and the HtmlEditor is focused.")]
		public new event HtmlElementEventHandler KeyPress
		{
			add
			{
				lock (KeyPressEvent)
				{
					Events.AddHandler(KeyPressEvent, value);
				}
			}
			remove
			{
				lock (KeyPressEvent)
				{
					Events.RemoveHandler(KeyPressEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="KeyPress" /> event.
		/// </summary>
		/// <param name="e"><see cref="HtmlElementEventArgs" /> object that provides the arguments for the event.</param>
		protected virtual void OnKeyPress(HtmlElementEventArgs e)
		{
			HtmlElementEventHandler handler = null;

			lock (KeyPressEvent)
			{
				handler = (HtmlElementEventHandler) Events[KeyPressEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised after the <see cref="Html" /> property value has changed.
		/// </summary>
		[Category("Property Changed")]
		[Description("Event raised after the Html property value has changed.")]
    public event EventHandler<HtmlChangedEventArgs> HtmlChanged
		{
			add
			{
				lock (HtmlChangedEvent)
				{
					Events.AddHandler(HtmlChangedEvent, value);
				}
			}
			remove
			{
				lock (HtmlChangedEvent)
				{
					Events.RemoveHandler(HtmlChangedEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="HtmlChanged" /> event.
		/// </summary>
		/// <param name="e"><see cref="EventArgs" /> object that provides the arguments for the event.</param>
    protected virtual void OnHtmlChanged(HtmlChangedEventArgs e)
		{
      EventHandler<HtmlChangedEventArgs> handler = null;

			lock (HtmlChangedEvent)
			{
        handler = (EventHandler<HtmlChangedEventArgs>) Events[HtmlChangedEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the editor has prepared the document and is ready for use by the user or code.
		/// </summary>
		[Category("State")]
		[Description("Event raised when the editor has prepared the document and is ready for use by the user or code.")]
		public event EventHandler EditorReady
		{
			add
			{
				lock (EditorReadyEvent)
				{
					Events.AddHandler(EditorReadyEvent, value);
				}
			}
			remove
			{
				lock (EditorReadyEvent)
				{
					Events.RemoveHandler(EditorReadyEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="EditorReady" /> event.
		/// </summary>
		/// <param name="e"><see cref="EventArgs" /> object that provides the arguments for the event.</param>
		protected virtual void OnEditorReady(EventArgs e)
		{
			EventHandler handler = null;

			lock (EditorReadyEvent)
			{
				handler = (EventHandler) Events[EditorReadyEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised before a control selection is made.
		/// </summary>
		[Category("Action")]
		[Description("Event raised before a control selection is made.")]
		public event EventHandler<HtmlEditorEventArgs> ControlSelected
		{
			add
			{
				lock (ControlSelectedEvent)
				{
					Events.AddHandler(ControlSelectedEvent, value);
				}
			}
			remove
			{
				lock (ControlSelectedEvent)
				{
					Events.RemoveHandler(ControlSelectedEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="ControlSelected" /> event.
		/// </summary>
		/// <param name="e"><see cref="HtmlEditorEventArgs" /> object that provides the arguments for the event.</param>
		protected virtual void OnControlSelected(HtmlEditorEventArgs e)
		{
			EventHandler<HtmlEditorEventArgs> handler = null;

			lock (ControlSelectedEvent)
			{
				handler = (EventHandler<HtmlEditorEventArgs>) Events[ControlSelectedEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the active selection has changed.
		/// </summary>
		[Category("Action")]
		[Description("Event raised when the active selection has changed.")]
		public event EventHandler<HtmlEditorEventArgs> SelectionChanged
		{
			add
			{
				lock (SelectionChangedEvent)
				{
					Events.AddHandler(SelectionChangedEvent, value);
				}
			}
			remove
			{
				lock (SelectionChangedEvent)
				{
					Events.RemoveHandler(SelectionChangedEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="SelectionChanged" /> event.
		/// </summary>
		/// <param name="e"><see cref="HtmlEditorEventArgs" /> object that provides the arguments for the event.</param>
		protected virtual void OnSelectionChanged(HtmlEditorEventArgs e)
		{
			EventHandler<HtmlEditorEventArgs> handler = null;

			lock (SelectionChangedEvent)
			{
				handler = (EventHandler<HtmlEditorEventArgs>) Events[SelectionChangedEvent];
			}

			if (handler != null)
				handler(this, e);
		}
		#endregion

		#region Event Handlers
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers", MessageId = "0#"), 
		System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.LinkDemand, Name = "FullTrust")]
		protected sealed override void OnDocumentCompleted(WebBrowserDocumentCompletedEventArgs e)
		{
			base.OnDocumentCompleted(e);

			InitializeDocument();

			// Two-pass initialization (see InitializeDocument comments)
			if (initialized)
			{
				if (!firstTimeInitialize)
          OnHtmlChanged(new HtmlChangedEventArgs(HtmlChangedReason.DocumentLoaded));
				else
				{
					firstTimeInitialize = false;
					OnEditorReady(EventArgs.Empty);
				}
			}
		}

		private void Document_Click(object sender, HtmlElementEventArgs e)
		{
			if (initialized)
			{
				IHTMLEventObj ev = WindowEvent;

				IHTMLElement element = ev.srcElement;

				bool? editable = HtmlSelection.IsEditableRecursive(element);

				if (editable.HasValue && !editable.Value)
				// mouse clicked outside of editable content
				{
					HtmlEditorCancelEventArgs args = HtmlEditorCancelEventArgs.FromEvent(Document, ev, e);

					OnMouseClickUneditableRegion(args);

					if (!args.Cancel)
						DefaultEditableElement.Focus();
				}
			}
		}

		private void Document_SelectionChange(object sender, EventArgs e)
		{
			if (initialized)
			{
				HtmlDocument document = Document;

				if (document != null)		// document was actually null once in testing while the application was shutting down
					OnSelectionChanged(HtmlEditorEventArgs.FromEvent(document, WindowEvent));
			}
		}

		private void Document_ControlSelect(object sender, EventArgs e)
		{
			if (initialized)
				OnControlSelected(HtmlEditorEventArgs.FromEvent(Document, WindowEvent));
		}

		private void Document_LosingFocus(object sender, HtmlElementEventArgs e)
		{
			if (initialized && IsDirty)
			{
				IsDirty = false;
        OnHtmlChanged(new HtmlChangedEventArgs(HtmlChangedReason.UserEdit));
			}
		}

		private void Body_KeyPress(object sender, HtmlElementEventArgs e)
		{
			OnKeyPress(e);
		}

		private void Body_Drop(object sender, EventArgs e)
		{
			HtmlEditorDragDropEventArgs args = HtmlEditorDragDropEventArgs.FromEvent(Document, WindowEvent);

			OnDragDrop(args);

			HtmlElement replacement = args.Replacement;

			if (replacement != null)
			{
				// cancel default processing
				args.Event.ReturnValue = false;

				Selection.Replace(replacement);

				// Insert a span with a single space after the replaced element so that if the user continues typing after, for example, HTML markup
				// is dropped into the editor, then the new text will not be contained by the dropped text - it will have its own element - the SPAN.
				HtmlElement element = replacement.InsertAdjacentElement(HtmlElementInsertionOrientation.AfterEnd, Document.CreateElement("SPAN"));
				element.InnerHtml = "&#160;";

				NotifyHtmlChanged(HtmlChangedReason.UserEdit);
			}
			else
			{
				string replacementHtml = args.ReplacementHtml;

				if (replacementHtml != null)
				{
					// cancel default processing
					args.Event.ReturnValue = false;

					HtmlSelection selection = Selection;

					selection.Replace(replacementHtml);

					selection.CollapseToEnd();

					NotifyHtmlChanged(HtmlChangedReason.UserEdit);
				}
			}
		}

		private void Body_DragOver(object sender, HtmlElementEventArgs e)
		{
			OnDragOver(HtmlEditorEventArgs.FromEvent(Document, WindowEvent, e));
		}

		private void Body_DragLeave(object sender, HtmlElementEventArgs e)
		{
			OnDragLeave(HtmlEditorEventArgs.FromEvent(Document, WindowEvent, e));
		}

		private void contextMenuStrip1_Opening(object sender, CancelEventArgs e)
		{
			if (!InitializeContextMenu())
				e.Cancel = true;
		}

		private void mnuUndo_Click(object sender, EventArgs e)
		{
			Commands.Undo.Execute();
		}

		private void mnuRedo_Click(object sender, EventArgs e)
		{
			Commands.Redo.Execute();
		}

		private void mnuCut_Click(object sender, EventArgs e)
		{
			Commands.Cut.Execute();
		}

		private void mnuCopy_Click(object sender, EventArgs e)
		{
			Commands.Copy.Execute();
		}

		private void mnuPaste_Click(object sender, EventArgs e)
		{
			Commands.Paste.Execute();
		}

		private void mnuBold_Click(object sender, EventArgs e)
		{
			Commands.Bold.Execute();
		}

		private void mnuItalic_Click(object sender, EventArgs e)
		{
			Commands.Italic.Execute();
		}

		private void mnuUnderline_Click(object sender, EventArgs e)
		{
			Commands.Underline.Execute();
		}

		private void mnuForeColorMore_Click(object sender, EventArgs e)
		{
			using (ColorDialog dialog = new ColorDialog())
			{
				dialog.AllowFullOpen = true;
				dialog.AnyColor = true;
				dialog.Color = (mnuForeColor.Image == null) ? Color.Black : (Color) mnuForeColor.Image.Tag;
				dialog.FullOpen = true;
				dialog.ShowHelp = true;
				dialog.SolidColorOnly = true;
				dialog.Color = SelectedForeColor;

				if (dialog.ShowDialog(this) == DialogResult.OK)
					Commands.ForeColor.Execute(true, ColorTranslator.ToHtml(dialog.Color));
			}
		}

		private void mnuBackColorMore_Click(object sender, EventArgs e)
		{
			using (ColorDialog dialog = new ColorDialog())
			{
				dialog.AllowFullOpen = true;
				dialog.AnyColor = true;
				dialog.Color = (mnuBackColor.Image == null) ? Color.Black : (Color) mnuBackColor.Image.Tag;
				dialog.FullOpen = true;
				dialog.ShowHelp = true;
				dialog.SolidColorOnly = true;
				dialog.Color = SelectedBackColor;

				if (dialog.ShowDialog(this) == DialogResult.OK)
					Commands.BackColor.Execute(true, ColorTranslator.ToHtml(dialog.Color));
			}
		}

		private void ForeColorMenuItem_Click(object sender, EventArgs e)
		{
			Color color = (Color) (sender as ToolStripMenuItem).Image.Tag;
			string strColor = ColorTranslator.ToHtml(color);

			Commands.ForeColor.Execute(false, strColor);
		}

		private void BackColorMenuItem_Click(object sender, EventArgs e)
		{
			Color color = (Color) (sender as ToolStripMenuItem).Image.Tag;
			string strColor = ColorTranslator.ToHtml(color);

			Commands.BackColor.Execute(false, strColor);
		}

		private void mnuFont_Click(object sender, EventArgs e)
		{
			using (FontDialog dialog = new FontDialog())
			{
				dialog.AllowScriptChange = false;
				dialog.AllowSimulations = true;
				dialog.AllowVectorFonts = true;
				dialog.AllowVerticalFonts = true;
				dialog.Color = SelectedForeColor;
				dialog.FixedPitchOnly = false;
				dialog.Font = SelectedFont;
				dialog.FontMustExist = true;
				dialog.ScriptsOnly = false;
				dialog.ShowApply = true;
				dialog.ShowColor = true;
				dialog.ShowEffects = true;
				dialog.ShowHelp = true;

				EventHandler ok = delegate(object sender2, EventArgs e2)
				{
					SelectedFont = dialog.Font;
					SelectedForeColor = dialog.Color;
				};

				dialog.Apply += ok;

				if (dialog.ShowDialog(this) == DialogResult.OK)
				{
					ok(null, null);
				}
			}
		}

		private void mnuSelectAll_Click(object sender, EventArgs e)
		{
			Commands.SelectAll.Execute();
		}

		private void mnuDelete_Click(object sender, EventArgs e)
		{
			Commands.Delete.Execute();
		}

		private void mnuFloatLeft_Click(object sender, EventArgs e)
		{
			Commands.FloatLeft.Execute();
		}

		private void mnuFloatRight_Click(object sender, EventArgs e)
		{
			Commands.FloatRight.Execute();
		}

		private void mnuIndent_Click(object sender, EventArgs e)
		{
			Commands.Indent.Execute();
		}

		private void mnuJustifyCenter_Click(object sender, EventArgs e)
		{
			Commands.JustifyCenter.Execute();
		}

		private void mnuJustifyLeft_Click(object sender, EventArgs e)
		{
			Commands.JustifyLeft.Execute();
		}

		private void mnuJustifyRight_Click(object sender, EventArgs e)
		{
			Commands.JustifyRight.Execute();
		}

		private void mnuUnindent_Click(object sender, EventArgs e)
		{
			Commands.Outdent.Execute();
		}

		private void mnuHyperlink_Click(object sender, EventArgs e)
		{
			Commands.CreateLink.Execute(true);
		}

		private void mnuPicture_Click(object sender, EventArgs e)
		{
			Commands.InsertImage.Execute(true);
		}

		private void mnuTable_Click(object sender, EventArgs e)
		{
			Commands.InsertTable.Execute(true);
		}

		private void mnuProperties_Click(object sender, EventArgs e)
		{
			IHTMLElement control = SelectedControl;

			if (control is IHTMLImgElement)
				Commands.InsertImage.Execute(true);		// edit mode is automatic

			if (control is IHTMLAnchorElement)
				Commands.CreateLink.Execute(true);		// edit mode is automatic
		}
		#endregion
	}

  /// <summary>
  /// Provides arguments for the <see cref="HtmlEditor.HtmlChanged"/> event.
  /// </summary>
  [Serializable]
  public class HtmlChangedEventArgs : EventArgs
  {
    #region Public Properties
    public HtmlChangedReason Reason
    {
      get
      {
        return reason;
      }
    }
    #endregion

    #region Private / Protected
    private readonly HtmlChangedReason reason;
    #endregion

    #region Constructors
    /// <summary>
    /// Constructs a new instance of the <see cref="HtmlChangedEventArgs" /> class.
    /// </summary>
    public HtmlChangedEventArgs(HtmlChangedReason reason)
    {
      if (!Enum.IsDefined(typeof(HtmlChangedReason), reason))
        throw new InvalidEnumArgumentException("reason", (int) reason, typeof(HtmlChangedReason));

      this.reason = reason;
    }
    #endregion
  }
}
